package com.hancheng.commoncloud.framework.aop;


import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.hancheng.commoncloud.business.entity.dto.UserIdDTO;
import com.hancheng.commoncloud.business.entity.vo.SysUserVO;
import com.hancheng.commoncloud.feign.service.UserFeignService;
import com.hancheng.commoncloud.framework.annotation.PassToken;
import com.hancheng.commoncloud.framework.contant.Constants;
import com.hancheng.commoncloud.framework.contant.RedisConstants;
import com.hancheng.commoncloud.framework.result.exception.CommonException;
import com.hancheng.commoncloud.framework.result.exception.ResultCode;
import com.hancheng.commoncloud.framework.util.ApiAssert;
import com.hancheng.commoncloud.framework.util.LocalUserInfoUtil;
import com.hancheng.commoncloud.framework.util.RedisUtils;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author 金笛
 * @date 2022/1/23 12:40
 */
@Slf4j
public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    private UserFeignService userFeignService;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private MapperFacade mapperFacade;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) {
        // 从 http 请求头中取出 token
        String authToken = httpServletRequest.getHeader(Constants.AUTH_TOKEN);
        log.info("authToken：{}", authToken);
        // 如果不是映射的方法直接通过
        if (!(object instanceof HandlerMethod)) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        // 检查是否有passtoken注释，有则跳过认证
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true;
            }
        } else {
            // 判断authToken，如果为空则证明前端没传此参数，抛出异常，给出提示
            ApiAssert.isTrue(StrUtil.isNotBlank(authToken), ResultCode.USER_NOT_LOGGED_IN, Constants.AUTH_TOKEN + "不存在，请重新登录");
            // 获取 token 中的 user id
            Long userId;
            try {
                userId = JWT.decode(authToken).getClaim("userId").asLong();
            } catch (JWTDecodeException jWTDecodeException) {
                throw new CommonException(ResultCode.USER_NOT_LOGGED_IN, "用户信息不存在，请重新登录");
            }
            //先从redis中取，如果没有则去数据库中找。
            Object cacheObject = redisUtils.getCacheObject(String.format(RedisConstants.USER_INFO.getKey(), userId));
            SysUserVO sysUserVO = mapperFacade.map(cacheObject, SysUserVO.class);

            if (ObjectUtil.isNull(sysUserVO)) {
                sysUserVO = userFeignService.getUserVOByUserId(UserIdDTO.builder().userId(userId).build()).getData();
                log.info("AOP认证用户信息redis没有记录，查询数据库，{}", sysUserVO);
            }
            ApiAssert.isNotNull(sysUserVO, ResultCode.USER_NOT_LOGGED_IN, "用户信息不存在，请重新登录");
            // 验证 token
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(sysUserVO.getPassword())).build();
            try {
                jwtVerifier.verify(authToken);
            } catch (JWTVerificationException jWTVerificationException) {
                throw new CommonException(ResultCode.USER_NOT_LOGGED_IN, Constants.AUTH_TOKEN + "验证失败，请重新登录");
            }
            //如果redis中不存在此key，则写入reids中
            if (!redisUtils.hasKey(String.format(RedisConstants.USER_INFO.getKey(), userId))) {
                log.info("AOP认证用户信息存入redis，{}", sysUserVO);
                redisUtils.setCacheObject(String.format(RedisConstants.USER_INFO.getKey(), userId), sysUserVO, Constants.ONE, TimeUnit.DAYS);
            }
            // 设置本地上下文
            LocalUserInfoUtil.set(sysUserVO);
            return true;
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }
}
